
package org.pentaho.di.trans.steps.addfield;

import com.google.common.annotations.VisibleForTesting;
import org.mozilla.javascript.Scriptable;
import org.pentaho.di.compatibility.Value;
import org.pentaho.di.core.CheckResultInterface;
import org.pentaho.di.core.database.DatabaseMeta;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.exception.KettleStepException;
import org.pentaho.di.core.exception.KettleXMLException;
import org.pentaho.di.core.injection.AfterInjection;
import org.pentaho.di.core.injection.Injection;
import org.pentaho.di.core.injection.InjectionSupported;
import org.pentaho.di.core.injection.NullNumberConverter;
import org.pentaho.di.core.plugins.KettleURLClassLoader;
import org.pentaho.di.core.row.RowMetaInterface;
import org.pentaho.di.core.variables.VariableSpace;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.repository.ObjectId;
import org.pentaho.di.repository.Repository;
import org.pentaho.di.trans.Trans;
import org.pentaho.di.trans.TransMeta;
import org.pentaho.di.trans.step.BaseStepMeta;
import org.pentaho.di.trans.step.StepDataInterface;
import org.pentaho.di.trans.step.StepInterface;
import org.pentaho.di.trans.step.StepMeta;
import org.pentaho.di.trans.step.StepMetaInterface;
import org.pentaho.di.trans.steps.fieldsplitter.DataTypeConverter;
import org.pentaho.di.trans.steps.scriptvalues_mod.ScriptValuesAddClasses;
import org.pentaho.metastore.api.IMetaStore;
import org.w3c.dom.Node;

import java.net.URL;
import java.util.Arrays;
import java.util.List;


@InjectionSupported(localizationPrefix = "AddField.Injection.", groups = {"FIELDS", "SCRIPTS"})
public class AddFieldMeta extends BaseStepMeta implements StepMetaInterface {
    private static Class<?> PKG = AddFieldMeta.class; // for i18n purposes, needed by Translator2!!



    @Injection(name = "FIELD_NAME", group = "FIELDS")
    private String[] fieldname;

    @Injection(name = "FIELD_RENAME_TO", group = "FIELDS")
    private String[] rename;

    @Injection(name = "FIELD_TYPE", group = "FIELDS", convertEmpty = true, converter = DataTypeConverter.class)
    private int[] type;

    @Injection(name = "FIELD_LENGTH", group = "FIELDS", convertEmpty = true, converter = NullNumberConverter.class)
    private int[] length;

    @Injection(name = "FIELD_PRECISION", group = "FIELDS", convertEmpty = true, converter = NullNumberConverter.class)
    private int[] precision;

    @Injection(name = "FIELD_REPLACE", group = "FIELDS")
    private boolean[] replace; // Replace the specified field.

    @Injection(name = "COMPATIBILITY_MODE")
    private boolean compatible;

    @Injection(name = "OPTIMIZATION_LEVEL")
    private String optimizationLevel;

    public AddFieldMeta() {
        super(); // allocate BaseStepMeta
        compatible = true;
        try {
            parseXmlForAdditionalClasses();
        } catch (Exception e) { /* Ignore */
        }
    }

    /**
     * @return Returns the length.
     */
    public int[] getLength() {
        return length;
    }

    /**
     * @param length The length to set.
     */
    public void setLength(int[] length) {
        this.length = length;
    }

    /**
     * @return Returns the name.
     */
    public String[] getFieldname() {
        return fieldname;
    }

    /**
     * @param fieldname The name to set.
     */
    public void setFieldname(String[] fieldname) {
        this.fieldname = fieldname;
    }

    /**
     * @return Returns the precision.
     */
    public int[] getPrecision() {
        return precision;
    }

    /**
     * @param precision The precision to set.
     */
    public void setPrecision(int[] precision) {
        this.precision = precision;
    }

    /**
     * @return Returns the rename.
     */
    public String[] getRename() {
        return rename;
    }

    /**
     * @param rename The rename to set.
     */
    public void setRename(String[] rename) {
        this.rename = rename;
    }

    /**
     * @return Returns the type.
     */
    public int[] getType() {
        return this.type;
    }

    @AfterInjection
    public void afterInjection() {
        // extend all fields related arrays to match the length of the fieldname array, as they may all be different
        // sizes, after meta injection
        extend(fieldname.length);
    }


    public void loadXML(Node stepnode, List<DatabaseMeta> databases, IMetaStore metaStore) throws KettleXMLException {
        readData(stepnode);
    }

    public void allocate(int nrfields) {
        fieldname = new String[nrfields];
        rename = new String[nrfields];
        type = new int[nrfields];
        for (int i = 0; i < nrfields; i++) {
            type[i] = -1;
        }
        length = new int[nrfields];
        for (int i = 0; i < nrfields; i++) {
            length[i] = -1;
        }
        precision = new int[nrfields];
        for (int i = 0; i < nrfields; i++) {
            precision[i] = -1;
        }
        replace = new boolean[nrfields];
    }

    /**
     * Extends all field related arrays so that they are the same size.
     *
     * @param nrfields
     */
    @VisibleForTesting
    void extend(int nrfields) {
        fieldname = extend(fieldname, nrfields);
        rename = extend(rename, nrfields);
        type = extend(type, nrfields);
        length = extend(length, nrfields);
        precision = extend(precision, nrfields);
        replace = extend(replace, nrfields);
    }

    private String[] extend(final String[] array, final int nrfields) {
        if (array == null) {
            return new String[nrfields];
        } else if (array.length < nrfields) {
            return Arrays.copyOf(array, nrfields);
        } else {
            return array;
        }
    }

    private int[] extend(final int[] array, final int nrfields) {
        if (array == null || array.length < nrfields) {
            int originalLength = array == null ? 0 : array.length;
            final int[] newArray = array == null ? new int[nrfields] : Arrays.copyOf(array, nrfields);
            for (int i = originalLength; i < nrfields; i++) {
                newArray[i] = -1;
            }
            return newArray;
        } else {
            return array;
        }
    }

    private boolean[] extend(final boolean[] array, final int nrfields) {
        if (array == null) {
            return new boolean[nrfields];
        } else if (array.length < nrfields) {
            return Arrays.copyOf(array, nrfields);
        } else {
            return array;
        }
    }

    public Object clone() {
        return null;
    }

    private void readData(Node stepnode) throws KettleXMLException {

    }

    public void setDefault() {

    }

    public void getFields(RowMetaInterface row, String originStepname, RowMetaInterface[] info, StepMeta nextStep,
                          VariableSpace space, Repository repository, IMetaStore metaStore) throws KettleStepException {
    }

    public String getXML() {
        return "";
    }

    public void readRep(Repository rep, IMetaStore metaStore, ObjectId id_step, List<DatabaseMeta> databases) throws KettleException {

    }

    public void saveRep(Repository rep, IMetaStore metaStore, ObjectId id_transformation, ObjectId id_step) throws KettleException {

    }

    public void check(List<CheckResultInterface> remarks, TransMeta transMeta, StepMeta stepMeta,
                      RowMetaInterface prev, String[] input, String[] output, RowMetaInterface info, VariableSpace space,
                      Repository repository, IMetaStore metaStore) {

    }

    public String getFunctionFromScript(String strFunction, String strScript) {
        String sRC = "";
        int iStartPos = strScript.indexOf(strFunction);
        if (iStartPos > 0) {
            iStartPos = strScript.indexOf('{', iStartPos);
            int iCounter = 1;
            while (iCounter != 0) {
                if (strScript.charAt(iStartPos++) == '{') {
                    iCounter++;
                } else if (strScript.charAt(iStartPos++) == '}') {
                    iCounter--;
                }
                sRC = sRC + strScript.charAt(iStartPos);
            }
        }
        return sRC;
    }

    public boolean getValue(Scriptable scope, int i, Value res, StringBuilder message) {


        return false;
    }

    public StepInterface getStep(StepMeta stepMeta, StepDataInterface stepDataInterface, int cnr,
                                 TransMeta transMeta, Trans trans) {
        return new AddField(stepMeta, stepDataInterface, cnr, transMeta, trans);
    }

    public StepDataInterface getStepData() {
        return new AddFieldData();
    }

    // This is for Additional Classloading
    public void parseXmlForAdditionalClasses() throws KettleException {

    }

    private static Class<?> LoadAdditionalClass(String strJar, String strClassName) throws KettleException {
        try {
            Thread t = Thread.currentThread();
            ClassLoader cl = t.getContextClassLoader();
            URL u = new URL("jar:file:" + strJar + "!/");
            // We never know what else the script wants to load with the class loader, so lets not close it just like that.
            @SuppressWarnings("resource")
            KettleURLClassLoader kl = new KettleURLClassLoader(new URL[]{u}, cl);
            Class<?> toRun = kl.loadClass(strClassName);
            return toRun;
        } catch (Exception e) {
            throw new KettleException(BaseMessages.getString(
                    PKG, "ScriptValuesMetaMod.Exception.UnableToLoadAdditionalClass"), e);
        }
    }

    public ScriptValuesAddClasses[] getAddClasses() {
        return null;
    }

    /**
     * @return the compatible
     */
    public boolean isCompatible() {
        return compatible;
    }

    /**
     * @param compatible the compatible to set
     */
    public void setCompatible(boolean compatible) {
        this.compatible = compatible;
    }

    public boolean supportsErrorHandling() {
        return true;
    }

    public String getDialogClassName() {
        return "org.pentaho.di.ui.trans.steps.addfield.AddFieldDialog";
    }

    /**
     * @return the replace
     */
    public boolean[] getReplace() {
        return replace;
    }

    /**
     * @param replace the replace to set
     */
    public void setReplace(boolean[] replace) {
        this.replace = replace;
    }

    public void setOptimizationLevel(String optimizationLevel) {
        this.optimizationLevel = optimizationLevel;
    }

    public String getOptimizationLevel() {
        return this.optimizationLevel;
    }
}
